1// SPDX-FileCopyrightText: (c) The Rust Project Contributors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// - https://github.com/rust-lang/rust/blob/master/LICENSE-MIT
4//
5//! Rust's Allocator compatible memory allocator for Skyrim.
6use super::SelflessAllocator;
7use core::ptr;
8use core::{alloc::Layout, hint, ptr::NonNull};
9use std::alloc::{alloc, alloc_zeroed, dealloc, realloc};
10use stdx::alloc::{AllocError, Global, non_null_empty_slice};
1112#[inline]
13#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
14#[allow(clippy::unused_self)]
15pub(crate) fn alloc_impl(layout: Layout, zeroed: bool) -> Result<NonNull<[u8]>, AllocError> {
16match layout.size() {
170 => Ok(non_null_empty_slice(layout)),
18// SAFETY: `layout` is non-zero in size,
19size => unsafe {
20let raw_ptr = if zeroed { alloc_zeroed(layout) } else { alloc(layout) };
21let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
22Ok(NonNull::slice_from_raw_parts(ptr, size))
23 },
24 }
25}
2627// SAFETY: Same as `Allocator::grow`
28#[inline]
29#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
30pub(crate) unsafe fn grow_impl(
31 ptr: NonNull<u8>,
32 old_layout: Layout,
33 new_layout: Layout,
34 zeroed: bool,
35) -> Result<NonNull<[u8]>, AllocError> {
36debug_assert!(
37 new_layout.size() >= old_layout.size(),
38"`new_layout.size()` must be greater than or equal to `old_layout.size()`"
39);
4041match old_layout.size() {
420 => alloc_impl(new_layout, zeroed),
4344// SAFETY: `new_size` is non-zero as `old_size` is greater than or equal to `new_size`
45 // as required by safety conditions. Other conditions must be upheld by the caller
46old_size if old_layout.align() == new_layout.align() => unsafe {
47let new_size = new_layout.size();
4849// `realloc` probably checks for `new_size >= old_layout.size()` or something similar.
50hint::assert_unchecked(new_size >= old_layout.size());
5152let raw_ptr = realloc(ptr.as_ptr(), old_layout, new_size);
53let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
54if zeroed {
55 raw_ptr.add(old_size).write_bytes(0, new_size - old_size);
56 }
57Ok(NonNull::slice_from_raw_parts(ptr, new_size))
58 },
5960// SAFETY: because `new_layout.size()` must be greater than or equal to `old_size`,
61 // both the old and new memory allocation are valid for reads and writes for `old_size`
62 // bytes. Also, because the old allocation wasn't yet deallocated, it cannot overlap
63 // `new_ptr`. Thus, the call to `copy_nonoverlapping` is safe. The safety contract
64 // for `dealloc` must be upheld by the caller.
65old_size => unsafe {
66let new_ptr = alloc_impl(new_layout, zeroed)?;
67 ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.cast().as_ptr(), old_size);
68 deallocate(ptr, old_layout);
69Ok(new_ptr)
70 },
71 }
72}
7374#[inline]
75#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
76unsafe fn deallocate(ptr: NonNull<u8>, layout: Layout) {
77if layout.size() != 0 {
78// SAFETY: `layout` is non-zero in size,
79 // other conditions must be upheld by the caller
80unsafe { dealloc(ptr.as_ptr(), layout) }
81 }
82}
8384unsafe impl SelflessAllocator for Global {
85#[inline]
86 #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
87fn allocate(layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
88 alloc_impl(layout, false)
89 }
9091#[inline]
92 #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
93fn allocate_zeroed(layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
94 alloc_impl(layout, true)
95 }
9697#[inline]
98 #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
99unsafe fn deallocate(ptr: NonNull<u8>, layout: Layout) {
100if layout.size() != 0 {
101// SAFETY: `layout` is non-zero in size,
102 // other conditions must be upheld by the caller
103unsafe { dealloc(ptr.as_ptr(), layout) }
104 }
105 }
106107#[inline]
108 #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
109unsafe fn grow(
110 ptr: NonNull<u8>,
111 old_layout: Layout,
112 new_layout: Layout,
113 ) -> Result<NonNull<[u8]>, AllocError> {
114// SAFETY: all conditions must be upheld by the caller
115unsafe { grow_impl(ptr, old_layout, new_layout, false) }
116 }
117118#[inline]
119 #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
120unsafe fn grow_zeroed(
121 ptr: NonNull<u8>,
122 old_layout: Layout,
123 new_layout: Layout,
124 ) -> Result<NonNull<[u8]>, AllocError> {
125// SAFETY: all conditions must be upheld by the caller
126unsafe { grow_impl(ptr, old_layout, new_layout, true) }
127 }
128129#[inline]
130 #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
131unsafe fn shrink(
132 ptr: NonNull<u8>,
133 old_layout: Layout,
134 new_layout: Layout,
135 ) -> Result<NonNull<[u8]>, AllocError> {
136debug_assert!(
137 new_layout.size() <= old_layout.size(),
138"`new_layout.size()` must be smaller than or equal to `old_layout.size()`"
139);
140141match new_layout.size() {
142// SAFETY: conditions must be upheld by the caller
1430 => {
144unsafe { Self::deallocate(ptr, old_layout) };
145Ok(non_null_empty_slice(new_layout))
146 }
147148// SAFETY: `new_size` is non-zero. Other conditions must be upheld by the caller
149new_size if old_layout.align() == new_layout.align() => unsafe {
150// `realloc` probably checks for `new_size <= old_layout.size()` or something similar.
151hint::assert_unchecked(new_size <= old_layout.size());
152153let raw_ptr = realloc(ptr.as_ptr(), old_layout, new_size);
154let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
155Ok(NonNull::slice_from_raw_parts(ptr, new_size))
156 },
157158// SAFETY: because `new_size` must be smaller than or equal to `old_layout.size()`,
159 // both the old and new memory allocation are valid for reads and writes for `new_size`
160 // bytes. Also, because the old allocation wasn't yet deallocated, it cannot overlap
161 // `new_ptr`. Thus, the call to `copy_nonoverlapping` is safe. The safety contract
162 // for `dealloc` must be upheld by the caller.
163new_size => unsafe {
164let new_ptr = Self::allocate(new_layout)?;
165 ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.cast().as_ptr(), new_size);
166Self::deallocate(ptr, old_layout);
167Ok(new_ptr)
168 },
169 }
170 }
171}